using System;
using System.Collections.Generic;
using AnimatingHair.Entity;
using AnimatingHair.Entity.PhysicalEntity;
using OpenTK;
using AnimatingHair.Auxiliary;

namespace AnimatingHair.Initializing
{
    class HairInitializer
    {
        private Hair hair;
        private Bust bust; // TODO: nech ma radsej IParticleDistributor, nez celu hlavu a scenu

        public Hair InitializeHair( Scene scene )
        {
            bust = scene.Bust;
            hair = new Hair( scene );

            distributeParticles();
            calculateArea();
            calculateH1H2();
            establishConnections();
            trimNeighbors();

            return hair;
        }

        #region Private initialize methods

        private void distributeParticles()
        {
            //IParticleDistributor distributor = new PrimitiveDistributor( -Const.s_r, 1, Const.Seed );
            IParticleDistributor distributor = new HeuristicBeamDistributor( Const.Seed, bust.Head );
            IEnumerable<ParticleCoordinate> coordinates = distributor.DistributeParticles( Const.ParticleCount );
            int i = 0;
            foreach ( ParticleCoordinate coordinate in coordinates )
            {
                HairParticle hp = new HairParticle( hair, i )
                {
                    Position = coordinate.Position,
                    Direction = coordinate.Direction,
                    Mass = Const.DefaultParticleMass
                };

                hp.U = coordinate.U;
                hp.V = coordinate.V;
                hp.S = coordinate.S;

                hair.Particles[ i ] = hp;

                i++;
            }
        }

        private void calculateArea()
        {
            foreach ( HairParticle hp in hair.Particles )
            {
                hp.Area = Math.Pow( hp.Mass / Const.DensityOfHairMaterial, 2.0 / 3.0 );
            }
        }

        private void calculateH1H2()
        {
            double min;
            double sum = 0;

            for ( int i = 0; i < hair.Particles.Length; i++ )
            {
                min = double.MaxValue;

                for ( int j = i + 1; j < hair.Particles.Length; j++ )
                {
                    double currDist = (hair.Particles[ i ].Position - hair.Particles[ j ].Position).Length;
                    if ( currDist < min )
                        min = currDist;
                }

                if ( min != double.MaxValue )
                    sum += min;
            }

            double avgMinDist = sum / hair.Particles.Length;

            hair.H1 = 1.1 * avgMinDist;
            hair.H2 = avgMinDist / 1.1;
        }

        private void establishConnections()
        {
            for ( int i = 0; i < hair.Particles.Length; i++ )
                for ( int j = i + 1; j < hair.Particles.Length; j++ )
                {
                    Vector3d x_ij = hair.Particles[ j ].Position - hair.Particles[ i ].Position;
                    double l = x_ij.Length;

                    if ( (l < 2 * hair.H1) && !(hair.Particles[ i ].IsRoot && hair.Particles[ j ].IsRoot) )
                    {
                        ParticlePair pair_ij = new ParticlePair
                        {
                            Particle1 = hair.Particles[ i ],
                            Particle2 = hair.Particles[ j ],
                            I = i,
                            J = j,
                            L = l,
                            X = x_ij,
                            Theta = Vector3d.CalculateAngle( hair.Particles[ i ].Direction, x_ij )
                        };

                        ParticlePair pair_ji = new ParticlePair
                        {
                            Particle1 = hair.Particles[ j ],
                            Particle2 = hair.Particles[ i ],
                            I = j,
                            J = i,
                            L = l,
                            X = -x_ij,
                            Theta = Vector3d.CalculateAngle( hair.Particles[ j ].Direction, -x_ij )
                        };

                        pair_ij.SinTheta = Math.Sin( pair_ij.Theta );
                        pair_ij.CosTheta = Math.Cos( pair_ij.Theta );
                        pair_ji.SinTheta = Math.Sin( pair_ji.Theta );
                        pair_ji.CosTheta = Math.Cos( pair_ji.Theta );

                        pair_ij.A = pair_ji.A = calculateA( pair_ij.Theta, pair_ji.Theta );

                        pair_ij.C = pair_ji.C = pair_ij.A * l;

                        if ( pair_ij.A < Const.a_0 )
                            continue;

                        pair_ij.K = pair_ji.K = pair_ij.C * Const.W( l, hair.H1 );

                        if ( hair.Particles[ j ].S > hair.Particles[ i ].S )
                        {
                            hair.Particles[ i ].NeighborsTip.Add( hair.Particles[ j ] );
                            hair.Particles[ j ].NeighborsRoot.Add( hair.Particles[ i ] );
                        }
                        else
                        {
                            hair.Particles[ i ].NeighborsRoot.Add( hair.Particles[ j ] );
                            hair.Particles[ j ].NeighborsTip.Add( hair.Particles[ i ] );
                        }

                        hair.ParticlePairs[ i, j ] = pair_ij;
                        hair.ParticlePairs[ j, i ] = pair_ji;
                    }
                }
        }

        private static double calculateA( double theta_ij, double theta_ji )
        {
            if ( Math.Cos( theta_ij ) * Math.Cos( theta_ji ) < 0 )
                return Math.Abs( Math.Cos( theta_ij ) - Math.Cos( theta_ji ) ) / 2.0;

            return 0;
        }

        private void trimNeighbors()
        {
            foreach ( HairParticle hp in hair.Particles )
            {
                hp.NeighborsRoot.Sort(
                    ( hp1, hp2 ) =>
                        -(hair.ParticlePairs[ hp, hp1 ].K.CompareTo( hair.ParticlePairs[ hp, hp2 ].K ))
                        );

                hp.NeighborsTip.Sort(
                    ( hp1, hp2 ) =>
                        -(hair.ParticlePairs[ hp, hp1 ].K.CompareTo( hair.ParticlePairs[ hp, hp2 ].K ))
                        );

                int countRoot = hp.NeighborsRoot.Count;
                int countTip = hp.NeighborsTip.Count;

                while ( countRoot + countTip > Const.N_n )
                {
                    if ( countTip == 0 && countRoot == 0 )
                        break;

                    if ( countRoot == 0 )
                    {
                        countTip--;
                        continue;
                    }

                    if ( countTip == 0 )
                    {
                        countRoot--;
                        continue;
                    }

                    if ( hair.ParticlePairs[ hp, hp.NeighborsRoot[ countRoot - 1 ] ].K < hair.ParticlePairs[ hp, hp.NeighborsTip[ countTip - 1 ] ].K )
                        countRoot--;
                    else
                        countTip--;
                }

                hp.NeighborsRoot.RemoveRange( countRoot, hp.NeighborsRoot.Count - countRoot );
                hp.NeighborsTip.RemoveRange( countTip, hp.NeighborsTip.Count - countTip );
            }
        }

        #endregion
    }
}
